﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Text;

namespace BeholdTV
{
    /// <remarks>
    /// /////////////////////////////////////////////////////////////////////////////
    /// 
    /// *** SDK for BeholdRC.dll ***                                              
    /// Version 2.0                                                                
    ///                                                                            
    /// Support:                                                                   
    /// http://tuner.beholder.ru                                                   
    /// mailto:support@beholder.ru                                                 
    ///
    /// /////////////////////////////////////////////////////////////////////////////
    /// 
    /// ИНФОРМАЦИЯ:
    /// Для работы с библиотекой BeholdRC.dll, настоятельно рекомендуется
    /// подгружать ее динамически через LoadLibrary/FreeLibrary.
    ///
    /// При установке новой версии приложения Behold TV, инсталлятор копирует
    /// свежую версиию библиотеки BeholdRC.dll в системную папку Windows
    ///
    /// /////////////////////////////////////////////////////////////////////////////
    ///
    /// ВНИМАНИЕ:
    /// Перед тем как работать c функциями <see cref="GetRemoteType"/> и 
    /// <see cref="GetRemoteCode"/>(<see cref="GetRemoteCodeEx"/>), 
    /// необходимо проинициализировать устройство функцией OpenCard.
    ///
    /// Функция UnInit должна быть обязательно вызвана при выходе 
    /// из приложения, даже если ни разу не была вызвана функция 
    /// OpenCard (в данном модуле это делается в методе <see cref="Dispose"/>).
    ///
    /// Опрос функции GetRemoteCode(Ex) можно выполнять по таймеру или использовать
    /// отдельный поток. Оптимальный интервал опроса 30-50мс.
    ///
    /// Функция GetRemoteCodeEx предназначена для работы с картами Behold TV 60x и
    /// позволяет получить код нажатой клавиши стороннего ПДУ. Также GetRemoteCodeEx
    /// совместима со всеми пультами ДУ Beholder и всеми картами Beholder.
    ///
    /// /////////////////////////////////////////////////////////////////////////////
    /// </remarks>
    /// <summary>
    /// Класс-обертка над BeholdRC.dll
    /// </summary>
    public class BeholdTVApiWrapper : IDisposable
    {
        private const string DllName = "BeholdRC.dll";
        
        private IntPtr handle;

        #region BeholdRC.dll API delegates

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate int GetCardCountDelegate();

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate string GetCardNameDelegate(int cardIndex);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate bool OpenCardDelegate(int cardIndex);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate RemoteControlType GetRemoteTypeDelegate();

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate uint GetRemoteCodeDelegate();

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate uint GetRemoteCodeExDelegate();

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate bool UnInitDelegate();

        #endregion

        public BeholdTVApiWrapper()
        {
            handle = Win32.LoadLibrary(DllName);
            if (handle == IntPtr.Zero)
                throw new BeholdTVDllLoadException(
                    string.Format(Properties.Resources.ErrorCannotLoadDll, DllName), 
                    new Win32Exception());

            try
            {
                GetCardCount = GetDelegateForFunction<GetCardCountDelegate>("GetCardCount");
                GetCardName = GetDelegateForFunction<GetCardNameDelegate>("GetCardName");
                OpenCard = GetDelegateForFunction<OpenCardDelegate>("OpenCard");
                GetRemoteType = GetDelegateForFunction<GetRemoteTypeDelegate>("GetRemoteType");
                GetRemoteCode = GetDelegateForFunction<GetRemoteCodeDelegate>("GetRemoteCode");
                GetRemoteCodeEx = GetDelegateForFunction<GetRemoteCodeExDelegate>("GetRemoteCodeEx");
                UnInit = GetDelegateForFunction<UnInitDelegate>("UnInit");
            }
            catch (BeholdTVDllLoadException)
            {
                FreeLibrary();
                throw; 
            }
        }

        #region Properties (API functions)

        public GetCardCountDelegate GetCardCount
        {
            get;
            private set;
        }

        public GetCardNameDelegate GetCardName
        {
            get;
            private set;
        }

        public OpenCardDelegate OpenCard
        {
            get;
            private set;
        }

        public GetRemoteTypeDelegate GetRemoteType
        {
            get;
            private set;
        }

        public GetRemoteCodeDelegate GetRemoteCode
        {
            get;
            private set;
        }

        public GetRemoteCodeExDelegate GetRemoteCodeEx
        {
            get;
            private set;
        }

        public UnInitDelegate UnInit
        {
            get;
            private set;
        }

        #endregion

        public void Dispose()
        {
            UnInit();
            FreeLibrary();
        }

        private void FreeLibrary()
        {
            if (!Win32.FreeLibrary(handle))
                throw new BeholdTVDllLoadException(
                    string.Format(Properties.Resources.ErrorUnloadingDll, DllName), 
                    new Win32Exception());
        }

        private T GetDelegateForFunction<T>(string functionName)
            where T : class
        {
            var function = Win32.GetProcAddress(handle, functionName);
            if (function == IntPtr.Zero)
                return null;

            var result = (object)Marshal.GetDelegateForFunctionPointer(function, typeof(T));
            if (result == null)
                throw new BeholdTVDllLoadException(
                    string.Format(Properties.Resources.ErrorCannotLoadDllFunction, DllName, functionName));

            return (T)result;
        }
    }
}
